﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ITAP.ShprossesBuilder.Modelling.Components.Constraints;
using System.Drawing;
using ITAP.Core.Modelling;

namespace ITAP.ShprossesBuilder.Modelling.Components
{
    /// <summary>
    /// Расширение методов для интерфейса <see cref="IComponent"/>.
    /// </summary>
    public static class ComponentExtensionMethods
    {
        /// <summary>
        /// Проверка, соединены ли между собой компоненты.
        /// </summary>
        /// <param name="component">Данный компонент.</param>
        /// <param name="otherComponent">Компонент, для которого проводится проверка.</param>
        /// <returns></returns>
        public static bool IsConnected(this IComponent component, IComponent otherComponent)
        {
            if (component.ConnectedComponents.Contains(otherComponent))
                return true;
            else
                return false;
        }

        private static Random randomProvider = new Random();
        /// <summary>
        /// Добавление компонента как внутреннего в данный компонент. Если SerialNumber компонента, который добавляется, равен 0, то 
        /// для него автоматически случайным образом сгенерируется SerialNumber, иначе SerialNumber останется неизменным. Нужно помннить, что
        /// SerialNumber должен быть уникальным для каждого компонента для всех моделей, поэтому использование добавления компонента с ненулевым 
        /// SerialNumber должно использоваться только тогда, когда этого требует ситуация (например, при некоторых Undo/Redo, когда исходный 
        /// компонент удаляется, а на его место становится сохранённый заранее с таким же SerialNumber, чтобы сохранить связи по этому 
        /// идентфикатору).
        /// </summary>
        /// <param name="component">Данный компонент.</param>
        /// <param name="innerComponent">Компонент для добавления.</param>
        public static void AddInnerComponent(this IComponent component, IComponent innerComponent)
        {
            if (!component.InnerComponents.Contains(innerComponent))
            {
                component.InnerComponents.Add(innerComponent);
                innerComponent.Parent = component;
                innerComponent.BeforeFixingConstraint += new ComponentConstraintEventDelegate(component.BeforeFixingInnerComponentConstraintEvent);
                innerComponent.AfterFixingConstraint += new ComponentConstraintEventDelegate(component.AfterFixingInnerComponentConstraintEvent);
                innerComponent.FatalModelError += new FatalModelErrorDelegate(component.FatalModelErrorEvent);

                if(innerComponent.SerialNumber == 0)
                    innerComponent.SerialNumber = randomProvider.Next();
            }
        }

        /// <summary>
        /// Получение предка внутреннего компонента по SerialNumber внутреннего компонента. Пробегает по всем внутренним компонентам 
        /// компонента, находит компонент с указанным SerialNumber и возвращает его предка.
        /// </summary>
        /// <param name="component">Данный компонент.</param>
        /// <param name="serialNumber">SerialNumber внутреннего компонента.</param>
        /// <returns>Найденный предок, иначе null.</returns>
        public static IComponent GetParentComponentByComponentSerialNumber(this IComponent component, int serialNumber)
        {
            IComponent parent = null;
            foreach (IComponent innerComponent in component.AllInnerComponents)
            {
                if (innerComponent.SerialNumber == serialNumber)
                {
                    parent = innerComponent.Parent;
                    break;
                }
            }
            return parent;
        }

        public static bool RemoveInnerComponentBySerialNumber(this IComponent component, int serialNumber)
        {
            IComponent componentToRemove = null;
            IComponent parent = null;
            foreach (IComponent innerComponent in component.AllInnerComponents)
            {
                if (innerComponent.SerialNumber == serialNumber)
                {
                    componentToRemove = innerComponent;
                    parent = componentToRemove.Parent;
                    break;
                }
            }
            return parent.RemoveInnerComponent(componentToRemove);
        }

        public static bool RemoveInnerComponent(this IComponent component, IComponent innerComponent)
        {
            if (component.InnerComponents.Remove(innerComponent))
            {
                innerComponent.Parent = null;
                innerComponent.RemoveAllInnerComponents();

                // Удаляем коннекты внутреннего компонента с другими компонентами модели (например, 
                // коннект соединителя в рамку с рамкой).
                List<IComponent> componentsToDisconnect = new List<IComponent>();
                foreach (IComponent connectedComponent in innerComponent.ConnectedComponents)
                {
                    componentsToDisconnect.Add(connectedComponent);
                }
                foreach (IComponent connectedComponent in componentsToDisconnect)
                {
                    ConnectionProvider.Disconnect(innerComponent, connectedComponent);
                    connectedComponent.ComponentsInTolerance.Clear();
                }

                innerComponent.ComponentsInTolerance.Clear();
                innerComponent.BeforeFixingConstraint -= component.BeforeFixingInnerComponentConstraintEvent;
                innerComponent.AfterFixingConstraint -= component.AfterFixingInnerComponentConstraintEvent;
                innerComponent.FatalModelError -= component.FatalModelErrorEvent;
                innerComponent = null;
                return true;
            }
            else
                return false;
        }

        public static void DisconnectFromAll(this IComponent component)
        {
            List<IComponent> componentsToDisconnect = new List<IComponent>();
            foreach (IComponent connectedComponent in component.ConnectedComponents)
            {
                componentsToDisconnect.Add(connectedComponent);
            }
            foreach (IComponent connectedComponent in componentsToDisconnect)
            {
                ConnectionProvider.Disconnect(component, connectedComponent);
            }
        }

        public static void MoveToZeroPoint(this IComponent component)
        {
            RectangleF bbox = component.BoundingBox;
            component.MoveWithOffset(new ModelVector(-bbox.X, -bbox.Y));
        }

        public static void MoveToZeroXPoint(this IComponent component)
        {
            RectangleF bbox = component.BoundingBox;
            component.MoveWithOffset(new ModelVector(-bbox.X, 0));
        }

        public static void MoveToZeroYPoint(this IComponent component)
        {
            RectangleF bbox = component.BoundingBox;
            component.MoveWithOffset(new ModelVector(0, -bbox.Y));
        }

        public static bool CheckCriticalConstraints(this IComponent component)
        {
            foreach (IConstraint constraint in component.Constraints)
                if (component.CriticalConstraintTypes.Contains(constraint.GetType()))
                    if (constraint.Check(component) == false)
                        return false;

            return true;
        }

        public static bool CheckAllConstraints(this IComponent component)
        {
            if (!component.CheckConstraints())
                return false;

            foreach (IComponent innerComponent in component.InnerComponents)
                if (!innerComponent.CheckAllConstraints())
                    return false;

            return true;
        }

        public static bool CheckAllCriticalConstraints(this IComponent component)
        {
            if (!component.CheckCriticalConstraints())
                return false;

            foreach (IComponent innerComponent in component.InnerComponents)
                if (!innerComponent.CheckAllCriticalConstraints())
                    return false;

            return true;
        }

        public static bool CheckConstraints(this IComponent component)
        {
            foreach (IConstraint constraint in component.Constraints)
                if (!constraint.Check(component))
                    return false;

            return true;
        }

        public static void RemoveAllInnerComponents(this IComponent component)
        {
            List<IComponent> componentsToRemove = new List<IComponent>();

            foreach (IComponent innerComponent in component.InnerComponents)
            {
                innerComponent.RemoveAllInnerComponents();
                componentsToRemove.Add(innerComponent);
            }

            foreach (IComponent innerComponent in componentsToRemove)
            {
                component.RemoveInnerComponent(innerComponent);
            }
        }

        public static ModelContainer GetRootContainer(this IComponent component)
        {
            IComponent parent = null;
            while (!(parent is ModelContainer))
            {
                parent = component.Parent;
                component = parent;
            }

            return parent as ModelContainer;
        }   
    }
}
